
package info.chrm.jtalkerserver;

import java.net.*;
import java.io.*;
import java.lang.reflect.Method;
import java.util.ArrayList;

/** This Class makes all plugIn procedures: 
 *  checks for available plugins and loads a needet one. */
public class JTalkerPlugInLoader {
   private String lastErrorMsg = null;		   // Last error message
   private JTalkerPlugInInterface allPlugins [];   // Array of JTalkerPlugInInterface objects
   private String searchURL;			   // PlugIn Dir
   private JTalkerServer theServer = null;	   // Reference to the server instance

   /** C'tor 
    * @param _theServer JTalkerServer instance.
    * @param _searchURL PlgIn directory. If null, the Loader tryes standard paths */
   public JTalkerPlugInLoader(JTalkerServer _theServer, String _searchURL) {
      this.theServer = _theServer;
      
      if(_searchURL == null || _searchURL.equals("")) {
	 // Use default dir
	 this.searchURL = "./plugins";
      } else
	 this.searchURL = _searchURL;

      String [] allPluginFiles = getPlugInNames(this.searchURL);      
      if(allPluginFiles != null)
         this.allPlugins = new JTalkerPlugInInterface [allPluginFiles.length];
      else {
	 // Disable plugins
	 this.allPlugins = null;
         return;
      }
      
      try {
	 // Preload all PlugIns
	 URL[] urlsToLoadFrom = new URL[]{new File(this.searchURL).toURL()};
	 URLClassLoader loader = new URLClassLoader(urlsToLoadFrom);
	 Class tmpClass;
	 
	 for( int i = 0; i < allPluginFiles.length; i++ ) { 
	    tmpClass = Class.forName(allPluginFiles[i], true, loader);
	    this.allPlugins[i] = (JTalkerPlugInInterface)tmpClass.newInstance();
	 }
      } catch (Exception _e) {
	 this.lastErrorMsg = "Error while initialize Plugins: " + _e.toString();
	 _e.printStackTrace();
	 
	 // Disable plugins
	 this.allPlugins = null;
      } 
   }

   /** Gets qualified names of all loaded PlugIns as a String ";;"-separated 
    *  @return a string of available plugins e.g.: "pluginA;;pluginB;;pluginN" */
   public String getLoadedPlugIns() {
      if(this.allPlugins == null)
	 return "";
      
      String tmpRetVal = "";
      int l = this.allPlugins.length;
      
      for( int i = 0; i < l; i++ ) {
	  tmpRetVal += this.allPlugins[i].getClass().getName();
	  if(i < (l - 1))
	     tmpRetVal += ";;";
      }

      return tmpRetVal;
   }

   /** Parses and invokes a method if plugIn is available, otherwise it returns null 
    *  @param _method Method for a PlugIn. The String looks like this: plugin::method("parameter")
    *  @param _caller User who call this method
    *  @return The result of the method or null if somthing went wrong - in this case read the last error. 
    *  @see getLastError() */
   public synchronized String invokePlugInMethod(String _method, JTalkerMember _caller, String _callerMsgID) {
      // Reset ErrorMsg
      this.lastErrorMsg = null;
      _method = _method.trim();
      
      try { 
	 
	 if(_method == null || _method.indexOf("::") == -1) {
	    this.lastErrorMsg = "Syntax error. Try module::method!";
	    return null;
	 }
	 
	 String [] tmpValues = _method.split("::");
	 
	 if(tmpValues.length != 2) {
	    this.lastErrorMsg = "Syntax error. Try module::method!";
	    return null;
	 }
	 
	 String plugin = tmpValues[0];
	 String method = tmpValues[1];
	 JTalkerPlugInInterface tmpPlugInObj = null;
	 
	 if(plugin == null || plugin.equals("") || method == null || method.equals("")) {
	    this.lastErrorMsg = "Syntax error";
	    return null;
	 }

	 // Std-Server-Method?
	 if(plugin.equalsIgnoreCase("std")) {
	    return invokeBuildInMethod(method, _caller);
	 }
      
	 if(this.allPlugins == null) {
	    this.lastErrorMsg = "No plugin loaded.";
	    return null;
	 }
	 
	 // Check if plugin is available
	 boolean hit = false;
	 
	 for( int i = 0; i < this.allPlugins.length; i++ ) {
	    if(plugin.equals(this.allPlugins[i].getClass().getName())) {
	       hit = true;
	       tmpPlugInObj = this.allPlugins[i];
	       break;
	    }
	 }

	 if(!hit) {
	    this.lastErrorMsg = "Plugin is not available on this server: " + plugin;
	    return null;
	 }
	
	 try {
	    Method [] plugInsMethods = tmpPlugInObj.getMethods();

	    method = method.trim();

	    // Say PlugIn who's calling
	    tmpPlugInObj.setCaller(_caller.getUserName(), _caller.getUserIP());

	    if(method.equals("getName()"))
	       return tmpPlugInObj.getName();
	    else if(method.equals("getDescription()"))
	       return tmpPlugInObj.getDescription();
	    else if(method.equals("getRelease()"))
	       return tmpPlugInObj.getRelease();
	    else if(method.equals("getMethods()")) {
	       String methodNamesInLine = "";
	       for( int i = 0; i < tmpPlugInObj.getMethods().length; i++ ) { 
		  methodNamesInLine += tmpPlugInObj.getMethods()[i].toString();
		  methodNamesInLine += ";;";
	       }
	       return methodNamesInLine;
	    } else {
	       // Parse and ivoke special plugin method
	       this.theServer.getLogger().write("+ Using Plugin: " + tmpPlugInObj.getName() + " caller: " + _caller.getUserName());
	       String tmpMethodName = (method.substring(0, method.indexOf('('))).trim();

	       String [] params = this.parseParams(method);
	       
	       for( int i = 0; i < plugInsMethods.length; i++ ) {
		  if(plugInsMethods[i].getName().equals(tmpMethodName)) {
		     // HIT!		     
		     String retVal = (String)plugInsMethods[i].invoke(tmpPlugInObj, params);

		     // Reset callers data
		     tmpPlugInObj.setCaller(null, null);

		     return retVal;
		  }
	       }

	      this.lastErrorMsg = "Method not declared: " + method;
	      return null;
	    }
	 
	    // DEBUG
	    //System.out.println(plugInsMethods[0].invoke(tmpPlugInObj, "10,12".split(",")));
	    //retVal = (String)plugInsMethods[1].invoke(tmpPlugInObj, null);
	    
	 } catch (Exception _e) {
	    System.out.println(
		  "*** ERROR: Can't invoke PlugIn method (" + plugin + "::" + method + "): " + _e.toString());
	    this.lastErrorMsg = "Can't invoke method: " + method + ". " + _e.toString();
	    return null;
	 } 

      } catch (Exception _e) {
	 _e.printStackTrace();	 
	 this.lastErrorMsg = "Can't invoke method: " + _method + ". " + _e.toString();
      }

      this.lastErrorMsg = "Unexpected Error. Can't invoke this method";
      return null;
   }

   /** Gets an array of PlugIn names in given URL or null. 
    *  @param _searchURL PlugIn directory. If null, use "./plugins" */
   public static String[] getPlugInNames(String _searchURL) {
      if(_searchURL == null)
	 _searchURL = "./plugins";
      
      File dir = new File(_searchURL);    
      
      FilenameFilter filter = new FilenameFilter() {
	 public boolean accept(File dir, String name) {
	    return name.endsWith(".class");
	 }
      };
      
      String plugins [] = dir.list(filter);
      
      if(plugins == null) {
	 System.out.println("NULL");
	 return null;
      }

      // Delete .class suffix
      for( int i = 0; i < plugins.length; i++ ) { 
	 plugins[i] = plugins[i].replaceAll(".class", "");
      }

      return plugins;
   }

   /** If an error occurs, this method gets an error string. Otherwise it returns null. */
   public String getLastError() {
      String tmpRetVal = this.lastErrorMsg;
      this.lastErrorMsg = null;
      return tmpRetVal;
   }

   /** Invoke Buildin Method 
    *  @param _method Method to invoke
    *  @param _caller User who call this method
    *  @return Return value of the invoked method
    * */
   private String invokeBuildInMethod(String _method, JTalkerMember _caller) {
      // remove spaces
      _method = _method.replaceAll(" ", "");
      
      if(_method.equals("getAllUsers()"))
	 return this.theServer.getAllGlobalUsers();
      
      else if(_method.equals("getAllPlugIns()"))
	 return this.getLoadedPlugIns();
      
      else if(_method.equals("getRelease()"))
	 return this.theServer.RELEASE;
      
      else if(_method.equals("logout()")) {
	 if(_caller.closeSession())
	    return "Session closed.";
	 else
	    return "Can't close this session. Maybe not webclient?";
	 
      } else if(_method.startsWith("createChatRoom(")) {
	 ////// CREATE CHATROOM /////////
	 String [] params = this.parseParams(_method);
	 if(params == null || params.length != 2)
	    return "Syntax error. Try: std::createChatRoom(\"roomName\", \"user1,user2,user3\")";

	 JTalkerMember jtm = this.theServer.addChatRoom(params[0]);
	 if(jtm == null)
	    return "Can't create this chatroom.";

         // Add the admin and all users
         String callerName = _caller.getUserName();
         jtm.setChatroomAdmin(callerName);
         String tmpUsers = params[1] + "," + callerName;
	 jtm.setChatroomUsers(tmpUsers);

         this.theServer.sendUpdateRequestTo(tmpUsers);

         return "Chatroom \"" + params[0] + "\" successfull created (Op: " + jtm.getChatroomAdmin() + ")";	 
	 
      } else if(_method.startsWith("removeChatRoom(")) {
	 ////// REMOVE CHATROOM /////////
	 String [] params = this.parseParams(_method);
	 if(params == null || params.length != 1)
	    return "Syntax error. Try: std::removeChatRoom(\"roomName\")";

	 if(!this.theServer.removeChatroom(params[0], _caller))
	    return "Can't remove chatroom \"" + params[0] + "\". Wrong name or you are not the Operator!";
	 else
	    return "Chatroom removed.";
	 
      } else if(_method.startsWith("addChatRoomUser(")) {
	 ////// ADD CHATROOM USER /////////
	 String [] params = this.parseParams(_method);
	 if(params == null || params.length != 2)
	    return "Syntax error. Try: std::addChatRoomUser(\"roomName\", \"userName\")";

	 if(!this.theServer.addChatroomUser(params[0], params[1], _caller))
	    return "Can't add user \"" + params[1] + "\". Wrong name or you are not the Operator!";
	 else
	    return "User added (" + params[1] + ").";
	
      } else if(_method.startsWith("removeChatRoomUser(")) {
	 ////// REMOVE CHATROOM USER /////////
	 String [] params = this.parseParams(_method);
	 if(params == null || params.length != 2)
	    return "Syntax error. Try: std::addChatRoomUser(\"roomName\", \"userName\")";

	 if(!this.theServer.removeChatroomUser(params[0], params[1], _caller))
	    return "Can't remove user \"" + params[1] + "\". Wrong name or you are not the Operator!";
	 else
	    return "User removed (" + params[1] + ").";
	 
      }  else if(_method.startsWith("getChatRoomUsers(")) {
	 ////// GET CHATROOM USERS /////////
	 String [] params = this.parseParams(_method);
	 if(params == null || params.length != 1)
	    return "Syntax error. Try: std::getChatRoomUsers(\"roomName\")";

	 return this.theServer.getChatroomUsers(params[0], _caller);
	 
      }  else if(_method.equals("getMethods()")) {
	 ////// GET ALL BUILD-IN METHODS /////////
	 String retVal = "std::getRelease();;std::getAllUsers();;std::getAllPlugIns();;std::logout();;";
	 retVal += "std::createChatRoom(\"roomName\", \"user1,user2,user3\");;";
	 retVal += "std::removeChatRoom(\"roomName\");;";
	 retVal += "std::addChatRoomUser(\"roomName\", \"userName\");;";
	 retVal += "std::removeChatRoomUser(\"roomName\", \"userName\");;";
	 retVal += "std::getChatRoomUsers(\"roomName\");;";
	 retVal += "<PLUGIN>::getName();;";
	 retVal += "<PLUGIN>::getDescription();;";
	 retVal += "<PLUGIN>::getRelease();;";
	 retVal += "<PLUGIN>::getMethods();;";
	 
	 return retVal;
      }
      
      return "NOT IMPLEMENTED YET!";
   }


   /** Parsing method parameter */
   private String [] parseParams(String _in) {
      String tmpParameters = _in.substring(_in.indexOf('(') + 1, _in.lastIndexOf(')'));
      String [] params = tmpParameters.split("\"[\\s]*,[\\s]*\"");
      if(params == null || params.length < 1 || (params.length == 1 && params[0].equals(""))) {
	 return null;
      }
      else {
	 params[0] = params[0].replaceFirst("\"", "");
	 params[params.length-1] = params[params.length-1].replaceFirst("\"", "");
      }
      
      return params; 
   }
  
} 
